package impl

import (
	"context"
	"encoding/hex"
	"errors"

	"hundsun.com/hsl/hschain/protos/config"

	"github.com/golang/protobuf/proto"
	"go.uber.org/atomic"
	bcomm "hundsun.com/hsl/hschain/blockchain/common"
	"hundsun.com/hsl/hschain/common/cache"
	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/common/types"
	protoscommon "hundsun.com/hsl/hschain/protos/common"
	pbledger "hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/store"
	dbm "hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/extend/ledger"
)

//BlockStore 区块存储
type BlockStore struct {
	ctx         context.Context
	ledgerDB    dbm.Database
	stateDB     dbm.Database
	height      *atomic.Uint64
	isParaChain bool
	log         logging.Log

	//记录当前活跃的block，减少数据库的访问提高效率
	activeBlocks *cache.Cache
}

// NewBlockStore 新建一个blockstore
func NewBlockStore(ctx context.Context, log logging.Log) *BlockStore {
	bsCtx, _ := context.WithCancel(ctx)
	blockstore := &BlockStore{
		ctx:          bsCtx,
		ledgerDB:     store.GetLedgerDB(),
		stateDB:      store.GetStateDB(),
		height:       atomic.NewUint64(0),
		isParaChain:  false,
		log:          log,
		activeBlocks: cache.New(),
	}
	return blockstore
}

// GetLedgerService 获取ledgerService
func (store *BlockStore) GetLedgerService() (*ledger.Service, error) {
	service, err := ledger.NewLedger(store.ledgerDB)
	if err != nil {
		store.log.Errorf("New Ledger failed, err", err.Error())
		return nil, err
	}

	return service, nil
}

// SaveGenesisBlock 保存创世块
func (store *BlockStore) SaveGenesisBlock(blk *pbledger.Block) error {
	if store.height.Load() > 0 {
		return nil
	}

	service, err := store.GetLedgerService()
	if err != nil {
		return err
	}

	err = service.SaveBlock(blk)
	if err != nil {
		return err
	}

	store.log.Debugf("Block height in blockstore update from %d to %d", store.height.Load(), blk.Header.Number)
	store.height.Store(blk.Header.Number)

	return nil
}

// SaveBlock 保存区块
func (store *BlockStore) SaveBlock(blk *pbledger.Block, batch dbm.Batch) error {
	blkHeight := blk.Header.Number
	if blkHeight > 0 && blkHeight <= store.height.Load() {
		store.log.Errorf("Block height %d is less than blockstore current height %d.", blkHeight, store.height.Load())
		return types.ErrAlreadyExisted
	}

	blkKV, err := ledger.BlockKV(blk)
	if err != nil {
		return err
	}
	return batch.Set(blkKV.Key, blkKV.Value)
}

// SaveBlocks 批量保存区块
func (store *BlockStore) SaveBlocks(blocks []*pbledger.Block) error {
	service, err := store.GetLedgerService()
	if err != nil {
		return err
	}

	var newBlkNum = blocks[0].Header.Number
	for _, blk := range blocks[1:] {
		nextBlkNum := blk.Header.Number
		if newBlkNum+1 == nextBlkNum {
			newBlkNum = nextBlkNum
		} else {
			return errors.New("blocks not continue")
		}
	}

	err = service.SaveBlocks(blocks)
	if err != nil {
		return err
	}

	store.height.Store(newBlkNum)
	return nil
}

// DelBlock 删除block信息从db数据库中
func (store *BlockStore) DelBlock(storeBatch dbm.Batch, executedBlk *pbledger.ExecutedBlock) (uint64, error) {
	height := executedBlk.Block.Header.Number
	if height == 0 {
		store.log.Warn("can't delete genesis block.")
		return 0, nil
	}

	//更新最新的block高度为前一个高度
	kv, _ := ledger.BlockNumberKV(height)
	storeBatch.Delete(kv.Key)

	kv, _ = ledger.BlockKV(executedBlk.Block)
	storeBatch.Delete(kv.Key)

	newHeight := height - 1
	store.height.Store(newHeight)
	// 删除区块对于平行链影响的相关处理

	hash := executedBlk.Block.Hash()
	store.log.Debug("DelBlock success", "blockheight", height, "hash", hex.EncodeToString(hash))
	return newHeight, nil
}

// GetBlock 根据高度查询区块
func (store *BlockStore) GetBlock(height uint64) (*pbledger.Block, error) {
	ldService, err := ledger.NewLedger(store.ledgerDB)
	if err != nil {
		return nil, err
	}

	return ldService.GetBlock(height)
}

// GetBlocks 根据高度批量查询区块
func (store *BlockStore) GetBlocks(start, end uint64) ([]*pbledger.Block, error) {
	ldService, err := ledger.NewLedger(store.ledgerDB)
	if err != nil {
		return nil, err
	}

	var blks []*pbledger.Block
	for i := start; i <= end; i++ {
		blk, err := ldService.GetBlock(i)
		if err != nil {
			return nil, err
		}
		blks = append(blks, blk)
	}
	return blks, nil
}

// SaveBlockNumber 保存区块高度
func (store *BlockStore) SaveBlockNumber(blkNum uint64, batch dbm.Batch) error {
	blkNumKV, err := ledger.BlockNumberKV(blkNum)
	if err != nil {
		return err
	}
	return batch.Set(blkNumKV.Key, blkNumKV.Value)
}

// SaveBlockSequence 保存区块序列号
func (store *BlockStore) SaveBlockSequence(blkSequence uint64, batch dbm.Batch) error {
	service, err := store.GetLedgerService()
	if err != nil {
		return err
	}
	currentSeq, _ := service.GetBlockSequence()
	if currentSeq >= blkSequence {
		return nil
	}
	blkSeqKV, err := ledger.BlockSequenceKV(blkSequence)
	if err != nil {
		return err
	}
	return batch.Set(blkSeqKV.Key, blkSeqKV.Value)
}

// SaveBlockExecResult 保存区块执行结果
func (store *BlockStore) SaveBlockExecResult(result *pbledger.BlockExecResult) error {
	batch := store.ledgerDB.NewBatch(true)
	// 保存执行区块执行结果, 可用于在其它表存在异常情况下恢复数据，该数据可对其进行裁剪
	kv, err := ledger.BlockExecResultKV(result)
	if err != nil {
		return err
	}
	err = batch.Set(kv.Key, kv.Value)
	if err != nil {
		return err
	}
	// 保存执行高度
	kv, err = ledger.MaxExecutedBlockNumberKV(result.GetBlockNumber())
	if err != nil {
		return err
	}
	err = batch.Set(kv.Key, kv.Value)
	if err != nil {
		return err
	}
	return batch.Write()
}

// GetBlockExecResult 获取区块执行结果
func (store *BlockStore) GetBlockExecResult(height uint64) (*pbledger.BlockExecResult, error) {
	ldService, err := ledger.NewLedger(store.ledgerDB)
	if err != nil {
		return nil, err
	}

	return ldService.GetBlockExecResult(height)
}

// GetExecutedBlock 根据高度获取执行后区块
func (store *BlockStore) GetExecutedBlock(height uint64) (*pbledger.ExecutedBlock, error) {
	var eb = &pbledger.ExecutedBlock{}
	var blk = &pbledger.Block{}

	blkExecRst, err := store.GetBlockExecResult(height)
	if err != nil {
		return nil, err
	}

	blk, err = store.GetBlock(height)
	if err != nil {
		return nil, err
	}

	eb.Block = blk
	eb.BlockExecResult = blkExecRst

	return eb, nil
}

// GetActiveBlock 根据高度获取获取区块
func (store *BlockStore) GetActiveBlock(height uint64) (*pbledger.ExecutedBlock, error) {
	eb, ok := store.activeBlocks.Get(bcomm.GenTempBlockKeyByHeight(height))
	if !ok {
		return nil, types.ErrNotFound
	}
	return eb.(*pbledger.ExecutedBlock), nil
}

// AddActiveBlock 添加活跃区块
func (store *BlockStore) AddActiveBlock(executedBlk *pbledger.ExecutedBlock) {
	activeBlkKey := bcomm.GenTempBlockKeyByHeight(executedBlk.Block.Header.Number)
	store.activeBlocks.Set(activeBlkKey, executedBlk)
}

// SaveTopicInfo 保存topic信息
func (store *BlockStore) SaveTopicInfo(topicType string, topicInfo []byte) error {
	kv, _ := ledger.AddTopicInfoKV(topicType, topicInfo)
	store.ledgerDB.Set(kv.Key, kv.Value)
	return nil
}

// GetTopicInfo 获取topic信息
func (store *BlockStore) GetTopicInfo(topicType string) ([]byte, error) {
	key := ledger.GenKey(dbm.NamespaceLedgerTopicInfo, topicType)
	topicBytes, err := store.ledgerDB.Get(key)
	if err != nil {
		return nil, err
	}

	return topicBytes, nil
}

// RecoverHeightFromDB 从数据库恢复高度
func (store *BlockStore) RecoverHeightFromDB() {
	topicBytes, err := store.GetTopicInfo(types.GetEventName(types.EventStoreCheckedBlockTopic))
	if err != nil {
		store.height.Store(0)
	}

	var blkChkMsg = &protoscommon.BlockCheckedMessage{}
	err = proto.Unmarshal(topicBytes, blkChkMsg)
	if err != nil {
		store.height.Store(0)
	}

	store.height.Store(blkChkMsg.BlockNumber)
}

// SaveCurParam save current param
func (store *BlockStore) SaveCurParam(curParam *config.SysParam) error {
	conParamInfo, err := proto.Marshal(curParam)
	if err != nil {
		return err
	}
	conParamKey := geneSysParamKey()
	err = store.ledgerDB.Set(conParamKey, conParamInfo)
	if err != nil {
		return err
	}
	return nil
}

// LoadCurParam load current param
func (store *BlockStore) LoadCurParam() (*config.SysParam, error) {
	if store.ledgerDB != nil {
		sysParamKey := geneSysParamKey()
		sysParamBytes, err := store.ledgerDB.Get(sysParamKey)
		if err != nil && err != types.ErrNotFound {
			return nil, err
		}
		if len(sysParamBytes) > 0 {
			var sysParam = &config.SysParam{}
			err = proto.Unmarshal(sysParamBytes, sysParam)
			if err != nil {
				return nil, err
			}
			return sysParam, nil
		}
	}
	return nil, nil
}

func geneSysParamKey() []byte {
	return []byte(dbm.NamespaceSystemParam + "con")
}
